package com.redis.impl;

import com.redis.RedisLock;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisTemplate;

import javax.annotation.Resource;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * redis的可重入自旋锁实现，在可重入分布式锁的实现基础上，增加自旋操作
 * 自旋：在被告知不可获取锁之后，并没有直接放弃，而是继续不断尝试获取锁
 * 实现方式：在加锁的方法中增加一个多次循环尝试的while
 *
 * @author : SongRenShuo
 * @date : 2024/08/28
 */
@Slf4j
public class RedisLockImplSpin implements RedisLock {
    @Resource
    private RedisTemplate<String, String> redisTemplate;
    private static final ThreadLocal<String> localUid = new ThreadLocal<String>();
    private static final ThreadLocal<Integer> localInteger = new ThreadLocal<Integer>();

    /**
     * 自旋锁的等待时间，1000s后再次尝试获取锁
     */
    private static final long REENTRY_SPIN_SLEEP = 1000;

    @Override
    public boolean tryLock(String lockKey, long timeout, TimeUnit unit) {
        boolean isLock = false;
        // 通过localUid判定本线程是否已经上锁
        if (localUid.get() == null) {
            String uuid = UUID.randomUUID().toString();
            localUid.set(uuid);
            while (true) {
                isLock = Boolean.TRUE.equals(redisTemplate.opsForValue().setIfAbsent(lockKey, uuid, timeout, unit));
                if (isLock) {
                    break;
                }
                try {
                    Thread.sleep(REENTRY_SPIN_SLEEP);
                } catch (InterruptedException e) {
                    Thread.currentThread().interrupt(); // 恢复中断状态
                    log.error("thread sleep err .......");
                }
            }
            log.info("lockKey：{} 线程：{} 获取锁成功", lockKey, Thread.currentThread().getName());
            localInteger.set(0);
        } else {
            log.info("lockKey：{} 线程：{} 获取重入锁成功", lockKey, Thread.currentThread().getName());
            isLock = true;
            // 如果已经上锁，则设置重入次数加一
            localInteger.set(localInteger.get() + 1);
        }

        return isLock;
    }

    @Override
    public void releaseLock(String lockKey) {
        if (localUid.get() != null && localUid.get().equalsIgnoreCase(redisTemplate.opsForValue().get(lockKey))) {
            if (localInteger.get() != null && localInteger.get() > 0) {
                log.info("lockKey：{} 线程：{} 重入锁释放", lockKey, Thread.currentThread().getName());
                // 如果已经是本线程，并且已经上锁,锁数量大于0
                localInteger.set(localInteger.get() - 1);
            } else {
                log.info("lockKey：{} 线程：{} 释放锁", lockKey, Thread.currentThread().getName());
                // 计数器减为0则解锁
                redisTemplate.delete(lockKey);
                localUid.remove();
                localInteger.remove();
            }
        }
    }
}
